這篇要延伸上一篇提到的單執行緒 ( single thread ),來講同步 synchronous 與非同步 asynchronous。
面試還蠻常被問到「請你解釋同步、非同步」、「什麼是 Event Loop」之類的~
JS 作為一門單執行緒語言,其程式碼在執行的時候,一個時間點僅能做一個指令,這也是我們常說的「同步」。初學時對同步這個詞一定很疑惑,聽起來很像可以同時做很多事情,結果剛好相反
◢▆▅▄▃ 崩╰(〒皿〒)╯潰▃▄▅▆◣ ,不過別擔心,寫久了就習慣了!
單執行緒:任務執行時是依序排隊處理
同步:一次只執行一件事情
以上兩個是 JS 本身的語言特性所規範的事情,其實這兩個名詞我覺得都是在講同個概念
非同步:看似可以同時執行多個事情
為什麼說是看似呢?因為這些非同步事件在佇列中還是會依序被叫到 call stack。
那麼重點來了,作為一個具有同步特性的語言,如何做到非同步?非同步又指什麼?
例如我們瀏覽網站的時候,網站內容幾乎是透過後端給予或者用 API 獲取第三方的資料,但這種動作我們是沒有辦法預估該事件多久內可以完成,且 JS 又是同步的,難道要等它載完才能夠做其他動作嗎?這是不太不可能的~
這時候 JS 就請到瀏覽器大大來幫忙,請瀏覽器提供非同步事件的 API (Web APIs),並在背後運行的時候多開一個區域放置這些「需要較多時間處理,無法預估處理時間的事件」,這個區域我們稱為事件佇列 Event Queue。
Web API 目前大致有這幾種:
除此之外,在 ES6 新增的 Promise 方法也是用來處理非同步請求,而且優先級別高於 Web API。
用圖簡言之
瀏覽器會把非同步事件放置在 event queue 裡,透過 event loop 來協調,當 call stack 裡的同步事件都處理完畢後,就會把 queue 裡的非同步事件調回到 call stack 執行。
請記得 call stack 才是真正執行的地方,其他都是等待區。
最近剛好看到 Linked 上的圖片分享,可惜這裡沒法發 gif 檔
總之就是事件執行的順序跟 TASK 的編號是一樣。
其中 Microtask 跟 Macrotask 就是 Promise 事件的優先度會大於 Web API 事件。
另外補充一個小地方,call stack 我們都記得是後進先出,但 queue 是先進先出,也就是先進來的事件會第一個被調到 call stack 執行。
最後用一個考到爛的經典考題來結尾 (゚д⊙)
可以先自己想一下哦!
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1);
}
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1);
}
ANS:
首先第一個 for 迴圈,for 本身是同步事件,所以立馬就會放入 call stack 執行,但是執行發現遇到了非同步的 setTimeout() ,於是這個 setTimeout() 被移到 event queue 裡。
這個迴圈跑了三次,也就是有三個 setTimeout() 等在 event queue 裡,接著透過 event loop 調到 call stack 執行。但為什麼是三個 3?因為我們用 var 去宣告變數 i,待同步的 for 跑完後,變數 i 為 3。加上三個 setTimeout() 並沒有自己的變數 i,因此透過作用域鍊向外尋找 i 的身影,所以都是 3。
與第一個 for 不同,第二個 for 透過 let 來宣告,let 的特性是區塊作用域,所以會保存當前的變數在每個 setTimeout() 內,所以是 0、1、2。
通常這個考題是會被接著詢問的,大家可以想一下,如果不用 let ,還有什麼方法可以確保 i 的值為 0、1、2 ?